import datetime

from decimal import Decimal

from database import db_session

from flask import session

from sqlalchemy import exc
from sqlalchemy import or_

from sqlalchemy.sql.expression import func

from managers import items_mgr
from models import *

def listar_solicitudes(nombre_proyecto):
    """Lista las solicitudes del proyecto
    @param nombre_proyecto: Nombre del proyecto actual"""
    lista=[]

    proyecto = db_session.query(Proyecto).filter_by\
    (nombre=nombre_proyecto).one()

    solicitudes = db_session.query(Solicitud).filter_by\
    (fk_proyecto=proyecto.id).all()

    for solicitud in solicitudes:
        usuario = db_session.query(Usuario).filter_by\
        (id=solicitud.usuario_creacion).one()

        fecha = solicitud.fecha_creacion
        year = fecha.year
        month = fecha.month
        day = fecha.day
        fechaDMA = str(day) + "/" + str(month) + "/" + str(year)

        elem = dict(id=solicitud.id, descripcion=solicitud.descripcion, \
        impacto=solicitud.impacto, estado=solicitud.estado, votos=\
        solicitud.votos_a_favor, fecha=fechaDMA, \
        solicitante=usuario.id_usuario)

        lista.append(elem)

    lista = sorted(lista, key=lambda elemento: elemento['id'])
    return lista

def validaciones_boton_eliminar_solicitud(seleccionados, id_usuario, 
    nombre_proyecto):
    """Verifica que se haya seleccionado una solicitud, y solo una.
    Ademas, controla que el usuario que intenta la eliminacion, sea el
    usuario creador de la misma
    @param seleccionados: Lista de solicitudes seleccionadas
    @param id_usuario: Identificador del usuario actual
    @param nombre_proyecto: Nombre del proyecto actual"""
    error = None

    if len(seleccionados) == 0: #No selecciono ninguna solicitud
        error = "Debe seleccionar una solicitud"
        return error
    if len(seleccionados) > 1: #Selecciono mas de una solicitud
        error = "Debe seleccionar una unica solicitud"
        return error

    proyecto = db_session.query(Proyecto).filter_by\
    (nombre=nombre_proyecto).one()

    if proyecto.estado == 'FIN':
        error = "El proyecto se encuentra Finalizado"
        return error

    id_solicitud = int(seleccionados[0])

    solicitud = db_session.query(Solicitud).filter_by\
    (id=id_solicitud).one()

    usuario = db_session.query(Usuario).filter_by\
    (id_usuario=id_usuario).one()

    if solicitud.estado != 'NUE':
        error = "No puede eliminar una solicitud ya enviada"
        return error

    if solicitud.usuario_creacion != usuario.id:
        error = "No puede eliminar una solicitud de otro usuario"
        return error

    return error

def obtener_items_solicitud(id_solicitud):
    """Devuelve la lista de items incluidos en la solicitud, 
    con sus atributos
    @param id_solicitud: Id de la solicitud actual"""
    lista=[]

    solicitud = db_session.query(Solicitud).filter_by\
    (id=id_solicitud).one()

    for item in solicitud.items:
        tipo_item = db_session.query(TipoItem).filter_by\
        (id=item.fk_tipo_item).one()

        elemento = dict(nombre=item.nombre, descripcion=\
        item.descripcion, observacion=item.observacion, version=\
        item.version, complejidad=item.complejidad, costo=\
        item.costo, tipo_item=tipo_item.nombre)

        lista.append(elemento)

    lista = sorted(lista, key=lambda elemento: elemento['nombre'])
    return lista

def obtener_valores_solicitud(id_solicitud):
    """Recupera los datos de la solicitud seleccionada
    @param id_solicitud: Identificador de la solicitud"""
    lista=[]

    solicitud = db_session.query(Solicitud).filter_by\
    (id=id_solicitud).one()

    solicitante = db_session.query(Usuario).filter_by\
    (id=solicitud.usuario_creacion).one()

    fecha = solicitud.fecha_creacion
    year = fecha.year
    month = fecha.month
    day = fecha.day
    fechaDMA = str(day) + "/" + str(month) + "/" + str(year)

    proyecto = db_session.query(Proyecto).filter_by\
    (id=solicitud.fk_proyecto).one()

    lista_miembros = ""
    for miembro in proyecto.miembros_comite:
        lista_miembros = lista_miembros + miembro.id_usuario + " "

    elemento = dict(identificador=solicitud.id, descripcion=\
    solicitud.descripcion, impacto=solicitud.impacto, solicitante=\
    solicitante.id_usuario, fecha=fechaDMA, lista_miembros=\
    lista_miembros)

    lista.append(elemento)

    return lista

def remover_solicitud(id_solicitud):
    """Elimina de la base de datos la solicitud seleccionada
    @param id_solicitud: Identificador de la solicitud"""
    try:
        solicitud = db_session.query(Solicitud).filter_by\
        (id=id_solicitud).one()

        #Se elimina la relacion entre la solicitud y los items
        solicitud_items = solicitud.items
        for item in list(solicitud_items):
            solicitud.items.remove(item)
            db_session.flush()

        #Se elimina la solicitud
        db_session.delete(solicitud)
        db_session.flush()

        db_session.commit()
    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return 'OK'

def validaciones_boton_enviar_solicitud(seleccionados, id_usuario, 
    nombre_proyecto):
    """Verifica que se haya seleccionado al menos una solicitud.
    Ademas, controla que el usuario que intenta el envio, sea el
    usuario creador de la misma
    @param seleccionados: Lista de solicitudes seleccionadas
    @param id_usuario: Identificador del usuario actual
    @param nombre_proyecto: Nombre del proyecto actual"""
    error = None

    if len(seleccionados) == 0: #No selecciono ninguna solicitud
        error = "Debe seleccionar al menos una solicitud"
        return error

    proyecto = db_session.query(Proyecto).filter_by\
    (nombre=nombre_proyecto).one()

    if proyecto.estado == 'FIN':
        error = "El proyecto se encuentra Finalizado"
        return error

    cantidad_miembros = len(proyecto.miembros_comite)
    if cantidad_miembros % 2 == 0:
        error = "El comite no esta formado correctamente. No se puede \
        enviar la solicitud"
        return error

    for elemento in seleccionados:
        id_solicitud = int(elemento)

        solicitud = db_session.query(Solicitud).filter_by\
        (id=id_solicitud).one()

        if solicitud.estado != 'NUE':
            error = "La solicitud ya fue enviada"
            return error

        usuario = db_session.query(Usuario).filter_by\
        (id_usuario=id_usuario).one()

        if solicitud.usuario_creacion != usuario.id:
            error = "No puede enviar una solicitud de otro usuario"
            return error

    return error

def postear_solicitud(seleccionados, id_usuario):
    """Envia la solicitud al comite de cambios para su consideracion
    @param id_solicitud: Identificador de la solicitud"""
    try:
        for elemento in seleccionados:
            id_solicitud = int(elemento)

            solicitud = db_session.query(Solicitud).filter_by\
            (id=id_solicitud).one()

            usuario = db_session.query(Usuario).filter_by\
            (id_usuario=id_usuario).one()

            fecha_actual = datetime.now()

            #Se cambia el estado de la solicitud y se inicializa el
            #contador de votos
            solicitud.estado = 'PEN'
            solicitud.votos_a_favor = 0
            solicitud.fecha_modificacion = fecha_actual
            solicitud.usuario_modificacion = usuario.id

            db_session.add(solicitud)
            db_session.flush()

        db_session.commit()
    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return 'OK'

def verificar_parametros_calculo_impacto(seleccionados):
    """Verifica que se haya seleccionado un item, y solo uno
    @param seleccionados: Lista de los items de la linea base seleccionada
    @return error: retorna un mensaje de error"""
    error = "OK"
    if len(seleccionados) == 0: #No selecciono ningun item
        error = "Debe seleccionar un item"
        return error

    if len(seleccionados) > 1: #Selecciono mas de un item
        error = "Debe seleccionar un unico item"
        return error

    return error
    
def calculo_impacto_hacia_adelante(id_item):
    """Realiza el calculo de impacto hacia adelante a partir de un item
    @param id_item: Identificador del item
    @return complejidad: valor del la operacion del calculo de impacto"""
    item = db_session.query(Item).filter_by(id=id_item).one()
    relaciones = db_session.query(Relacion).filter_by(\
    fk_item_origen=item.id).filter_by(estado="ACT").all()
    complejidad_derecha = 0
    if len(relaciones) > 0:
        
        for relacion in relaciones:
            complejidad_derecha = complejidad_derecha + calculo_impacto_hacia_adelante(\
            relacion.fk_item_destino)

    complejidad_item = item.complejidad
    
    return complejidad_item + complejidad_derecha

def calculo_impacto_hacia_atras(id_item):
    """Realiza el calculo de impacto hacia atras a partir de un item
    @param id_item: Identificador del item
    @return complejidad: valor del la operacion del calculo de impacto"""
    item = db_session.query(Item).filter_by(id=id_item).one()
    relaciones = db_session.query(Relacion).filter_by(\
    fk_item_destino=item.id).filter_by(estado="ACT").all()
    complejidad_izquierda = 0
    if len(relaciones) > 0:
        for relacion in relaciones:
            complejidad_izquierda = complejidad_izquierda + calculo_impacto_hacia_atras(\
            relacion.fk_item_origen)
            
    complejidad_item = item.complejidad
    
    return complejidad_izquierda + complejidad_item

def calculo_impacto_total(id_item):
    """Realiza la sumatoria del valor del calculo de impacto hacia atras 
    mas la complejidad del item mas valor del calculo de impacto hacia adelante
    @param id_item: Identificador del item
    @return complejidad: valor del la operacion del calculo de impacto"""
    item = db_session.query(Item).filter_by(id=id_item).one()
    complejidad_atras = calculo_impacto_hacia_atras(id_item)
    complejidad_item = item.complejidad
    complejidad_adelante = calculo_impacto_hacia_adelante(id_item)
    
    return complejidad_atras + complejidad_adelante - complejidad_item

def obtener_items_proyecto_solicitud(id_usuario, nombre_proyecto):
    """Obtiene los items del proyecto que se encuentren en una linea 
    base y a los cuales tiene acceso el usuario actual
    @param id_usuario: Usuario actual
    @param nombre_proyecto: Nombre del proyecto"""
    proyecto = db_session.query(Proyecto).filter_by(\
    nombre=nombre_proyecto).one()

    usuario = db_session.query(Usuario).filter_by(\
    id_usuario=id_usuario).one()

    #Se crea una lista con las fases a las cuales tiene acceso el 
    #usuario actual
    lista_fases = []
    for rol in usuario.roles:
        sw = False
        for item in lista_fases:
            if rol.fk_fase == item:
                sw = True
        if sw == False:
            lista_fases.append(rol.fk_fase)

    #Se crea una lista con las lineas base de las fases a las cuales
    #tiene acceso el usuario actual
    lista_lineas_base = []
    for fase in proyecto.fases:
        if fase.id in lista_fases:
            lineas_base = db_session.query(LineaBase).filter(\
            LineaBase.fk_fase == fase.id).filter(\
            LineaBase.estado != 'ELI').all()

            for lb in lineas_base:
                lista_lineas_base.append(lb.id)

    #Se crea la lista de items de las lineas base accesibles al usuario
    #actual
    lista = []
    for linea in lista_lineas_base:
        linea_base = db_session.query(LineaBase).filter_by(\
        id=linea).one()

        for item in linea_base.items:
            calc_izq = calculo_impacto_hacia_atras(item.id)
            calc_der = calculo_impacto_hacia_adelante(item.id)
            calc_tot = calculo_impacto_total(item.id)
            
            tipo_item = db_session.query(TipoItem).filter_by(\
            id=item.fk_tipo_item).one()

            fase = db_session.query(Fase).filter_by(\
            id=tipo_item.fk_fase).one()

            item = dict(id=item.id, nombre=item.nombre, \
            descripcion=item.descripcion, observacion=item.observacion, \
            estado=item.estado, version=item.version, \
            complejidad=item.complejidad, costo=item.costo, \
            tipo_item=tipo_item.nombre, calc_izq=calc_izq, \
            calc_der=calc_der, calc_tot=calc_tot, fase=fase.nombre)
            
            lista.append(item)
    
    return lista

def verificar_crear_solicitud(descripcion, seleccionados):
    """Verifica que se haya seleccionado al menos un item
    @param descripcion: descripcion de la solicitud de cambio
    @param seleccionados: Lista de items seleccionados
    @return error: si no hay error None, en caso contrario el mensaje de error"""
    error = None
    if len(descripcion) == 0:
        error = "Debe ingresar una descripcion"
        return error
    
    if len(seleccionados) == 0: #No selecciono ningun item
        error = "Debe seleccionar un item"

    return error
    
def insertar_solicitud(id_usuario, nombre_proyecto, descripcion, \
    items_seleccionados):
    """Inserta una solicitud de cambio
    @param id_usuario: Identificador del usuario actual
    @param nombre_proyecto: Nombre del proyecto actual
    @param descripcion: Descripcion de la solicitud de cambio
    @param items_seleccionados: Items seleccionados para la solicitud 
    de cambio
    @return error: Si no hay error None, en caso contrario el mensaje 
    de error"""
    try:
        error = None
        fecha_actual = datetime.now()
        
        usuario = db_session.query(Usuario).filter_by(\
        id_usuario=id_usuario).one()
        
        proyecto = db_session.query(Proyecto).filter_by(\
        nombre=nombre_proyecto).one()
        
        #Se setean los valores de la nueva solicitud
        solicitud = Solicitud()
        solicitud.descripcion = descripcion
        solicitud.fk_proyecto = proyecto.id
        solicitud.estado = "NUE"
        solicitud.votos_a_favor = 0
        solicitud.impacto = 0
        solicitud.fecha_creacion = fecha_actual
        solicitud.usuario_creacion = usuario.id
        solicitud.fecha_modificacion = fecha_actual
        solicitud.usuario_modificacion = usuario.id
        
        lista = []
        impacto = 0
        #se itera sobre los items seleccionados
        for item_seleccionado in items_seleccionados:
            item_seleccionado_numerico = int(item_seleccionado)
            
            item = db_session.query(Item).filter_by(\
            id=item_seleccionado_numerico).one()
            solicitud.items.append(item)
            db_session.flush()
            
            sw = False
            for elemento in lista:
                #verifica que le item seleccionado ya se encuentre en la lista
                if item_seleccionado_numerico == elemento['id']:
                    sw = True
            
            #si el item seleccionado no se encuentra en la lista
            #se calcula su impacto por la derecha y se suma al impacto total
            if sw == False:
                #se llama a la funcion recursiva calculo_complejidad_derecha()
                impacto = impacto + calculo_complejidad_derecha(\
                item_seleccionado_numerico, lista)
        
        solicitud.impacto = impacto
        
        db_session.add(solicitud)
        db_session.commit()
        
    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message
    
    return error

def calculo_complejidad_derecha( id_item, lista):
    """Realiza el calculo de impacto hacia adelante a partir de un item
    @param id_item: Identificador del item
    @param lista: Lista de los item que ya se tuvieron en cuenta en el calculo de impacto
    @return complejidad: Valor del la operacion del calculo de impacto"""
    item = db_session.query(Item).filter_by(id=id_item).one()
    
    relaciones = db_session.query(Relacion).filter_by(\
    fk_item_origen=item.id).filter_by(estado="ACT").all()
    
    complejidad_derecha = 0
    #se comprueba si el item posee relaciones
    if len(relaciones) > 0:
        
        for relacion in relaciones:
            sw = False
            for elemento in lista:
                #se verifica que el item destino de la relacion se 
                #encuetre en la lista temporal
                if elemento['id'] == relacion.fk_item_destino:
                    sw = True
            
            #si no se encuentra en la lista temporal se agrega el item 
            #a la lista y se calcula su impacto
            if sw == False:
                elemento = dict(id=relacion.fk_item_destino)
                lista.append(elemento)
                
                #se invoca a la funcion recursiva
                complejidad_derecha = complejidad_derecha + calculo_complejidad_derecha(\
                relacion.fk_item_destino, lista)           
            
    complejidad_item = item.complejidad
    
    return complejidad_item + complejidad_derecha

def validaciones_boton_modificar_solicitud(seleccionados, id_usuario, 
    nombre_proyecto):    
    """Verifica que se haya seleccionado una solicitud, y solo una.
    Ademas, controla que el usuario que intenta la modificacion, sea el
    usuario creador de la misma
    @param seleccionados: Lista de solicitudes seleccionadas
    @param id_usuario: Identificador del usuario actual
    @param nombre_proyecto: Nombre del proyecto actual"""
    error = None
    if len(seleccionados) == 0: #No selecciono ninguna solicitud
        error = "Debe seleccionar una solicitud"
        return error
    if len(seleccionados) > 1: #Selecciono mas de una solicitud
        error = "Debe seleccionar una unica solicitud"
        return error

    proyecto = db_session.query(Proyecto).filter_by\
    (nombre=nombre_proyecto).one()

    if proyecto.estado == 'FIN':
        error = "El proyecto se encuentra Finalizado"
        return error

    id_solicitud = int(seleccionados[0])

    solicitud = db_session.query(Solicitud).filter_by\
    (id=id_solicitud).one()

    usuario = db_session.query(Usuario).filter_by\
    (id_usuario=id_usuario).one()

    if solicitud.estado != 'NUE':
        error = "No puede modificar una solicitud ya enviada"
        return error

    if solicitud.usuario_creacion != usuario.id:
        error = "No puede modificar una solicitud de otro usuario"
        return error

    return error
    
def obtener_items_modificar_solicitud(id_solicitud, id_usuario, \
    nombre_proyecto):
    """Obtiene la lista de los items asociados a la solicitud 
    seleccionada
    @param id_solicitud: Identificador de la solicitud de cambio
    @param id_usuario: Identificador del usuario actual
    @param nombre_proyecto: Nombre del proyecto actual
    @return lista: Lista de items de la solicitud"""
    lista_items = obtener_items_proyecto_solicitud(id_usuario, \
    nombre_proyecto)

    solicitud = db_session.query(Solicitud).filter_by(\
    id=id_solicitud).one()

    lista = []
    for item_proyecto in lista_items:
        sw = False
        identificador = item_proyecto['id']
        for item_solicitud in solicitud.items:
            if identificador == item_solicitud.id:
                sw = True

        nombre = item_proyecto['nombre']
        descripcion = item_proyecto['descripcion']
        observacion = item_proyecto['observacion']
        estado = item_proyecto['estado']
        version = item_proyecto['version']
        complejidad = item_proyecto['complejidad']
        costo = item_proyecto['costo']
        tipo_item = item_proyecto['tipo_item']
        calc_izq = item_proyecto['calc_izq']
        calc_der = item_proyecto['calc_der']
        calc_tot = item_proyecto['calc_tot']
        fase = item_proyecto['fase']
        marcado = None
        if sw == True:
            marcado = 'True'
        else:
            marcado = 'False'

        elemento = dict(id=identificador, nombre=nombre, descripcion=\
        descripcion, observacion=observacion, version=version, \
        complejidad=complejidad, costo=costo, tipo_item=tipo_item, \
        calc_izq=calc_izq, calc_der=calc_der, calc_tot=calc_tot, \
        marcado=marcado, fase=fase)

        lista.append(elemento)

    return lista

def obtener_items_solicitud_marcados(id_usuario, \
    nombre_proyecto, seleccionados):
    """Obtiene la lista de los items asociados a la solicitud 
    seleccionada
    @param id_usuario: Identificador del usuario actual
    @param nombre_proyecto: Nombre del proyecto actual
    @return lista: Lista de items de la solicitud"""
    lista_items = obtener_items_proyecto_solicitud(id_usuario, \
    nombre_proyecto)

    lista = []
    for item_proyecto in lista_items:
        sw = False
        identificador = str(item_proyecto['id'])
        if identificador in seleccionados:
            sw = True

        nombre = item_proyecto['nombre']
        descripcion = item_proyecto['descripcion']
        observacion = item_proyecto['observacion']
        estado = item_proyecto['estado']
        version = item_proyecto['version']
        complejidad = item_proyecto['complejidad']
        costo = item_proyecto['costo']
        tipo_item = item_proyecto['tipo_item']
        calc_izq = item_proyecto['calc_izq']
        calc_der = item_proyecto['calc_der']
        calc_tot = item_proyecto['calc_tot']
        marcado = None
        if sw == True:
            marcado = 'True'
        else:
            marcado = 'False'

        elemento = dict(id=identificador, nombre=nombre, descripcion=\
        descripcion, observacion=observacion, version=version, \
        complejidad=complejidad, costo=costo, tipo_item=tipo_item, \
        calc_izq=calc_izq, calc_der=calc_der, calc_tot=calc_tot, \
        marcado=marcado)

        lista.append(elemento)

    return lista

def verificar_modificar_solicitud(descripcion, seleccionados):
    """Verifica que se haya seleccionado al menos un item
    @param descripcion: Descripcion de la solicitud de cambio
    @param seleccionados: Lista de items seleccionados
    @return error: si no hay error None, en caso contrario el mensaje de error"""
    error = None
    if len(descripcion) == 0:
        error = "Debe ingresar una descripcion"
        return error

    if len(seleccionados) == 0: #No selecciono ningun item
        error = "Debe seleccionar al menos un item"

    return error

def modificacion_solicitud(id_usuario, id_solicitud, descripcion, \
    items_seleccionados):
    """Modifica una solicitud de cambio
    @param id_usuario: Identificador del usuario
    @param id_solicitud: identificador de la solicitud
    @param descripcion: Descripcion de la solicitud de cambio
    @param seleccionados: Lista de items de la solicitud de cambio
    @return error: si no hay error None, en caso contrario el mensaje de error"""
    try:
        error = None
        fecha_actual = datetime.now()

        usuario = db_session.query(Usuario).filter_by(id_usuario=id_usuario).one()

        solicitud = db_session.query(Solicitud).filter_by(\
        id=id_solicitud).one()

        #se eliminan los items asociados a la solicitud
        solicitud_items = solicitud.items
        for item in list(solicitud_items):
            solicitud.items.remove(item)
            db_session.flush()

        lista = []
        impacto = 0
        #se itera sobre los items seleccionados
        for item_seleccionado in items_seleccionados:
            item_seleccionado_numerico = int(item_seleccionado)

            item = db_session.query(Item).filter_by(\
            id=item_seleccionado_numerico).one()
            solicitud.items.append(item)
            db_session.flush()

            sw = False
            for elemento in lista:
                #verifica que le item seleccionado ya se encuentre en la lista
                if item_seleccionado_numerico == elemento['id']:
                    sw = True

            #si el item seleccionado no se encuentra en la lista
            #se calcula su impacto por la derecha y se suma al impacto total            
            if sw == False:
                #se llama a la funcion recursiva calculo_complejidad_derecha()
                impacto = impacto + calculo_complejidad_derecha(\
                item_seleccionado_numerico, lista)

        #se setean los valores de la nueva solicitud        
        solicitud.descripcion = descripcion
        solicitud.impacto = impacto
        solicitud.fecha_modificacion = fecha_actual
        solicitud.usuario_modificacion = usuario.id

        db_session.add(solicitud)
        db_session.commit()

    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return error

def validaciones_boton_ver_solicitud(seleccionados):    
    """Verifica que se haya seleccionado una solicitud, y solo una.
    @param seleccionados: Lista de solicitudes seleccionadas"""
    error = None
    if len(seleccionados) == 0: #No selecciono ninguna solicitud
        error = "Debe seleccionar una solicitud"
        return error
    if len(seleccionados) > 1: #Selecciono mas de una solicitud
        error = "Debe seleccionar una unica solicitud"
        return error

    return error

def obtener_items_ver_solicitud(id_solicitud):
    """Obtiene la lista de los items asociados a la solicitud 
    seleccionada
    @param id_solicitud: Identificador de la solicitud de cambio
    @return lista: Lista de items de la solicitud"""
    solicitud = db_session.query(Solicitud).filter_by(\
    id=id_solicitud).one()

    usuario_creador = db_session.query(Usuario).filter_by(\
    id=solicitud.usuario_creacion).one()

    lista_items = solicitud.items

    lista = []
    for item in lista_items:
        identificador = item.id
        nombre = item.nombre
        descripcion = item.descripcion
        observacion = item.observacion
        estado = item.estado
        version = item.version
        complejidad = item.complejidad
        costo = item.costo

        calc_izq = calculo_impacto_hacia_atras(item.id)
        calc_der = calculo_impacto_hacia_adelante(item.id)
        calc_tot = calculo_impacto_total(item.id)

        tipo = db_session.query(TipoItem).filter_by(\
        id=item.fk_tipo_item).one()

        fa = db_session.query(Fase).filter_by(\
        id=tipo.fk_fase).one()

        tipo_item = tipo.nombre
        fase = fa.nombre

        elemento = dict(id=identificador, nombre=nombre, descripcion=\
        descripcion, observacion=observacion, version=version, \
        complejidad=complejidad, costo=costo, tipo_item=tipo_item, \
        calc_izq=calc_izq, calc_der=calc_der, calc_tot=\
        calc_tot, fase=fase)

        lista.append(elemento)

    return lista

def validaciones_administrar_comite(seleccionados):
    error = None
    if len(seleccionados) == 0: #No selecciono ningun proyecto
        error = "Debe seleccionar un proyecto"
        return error
    if len(seleccionados) > 1: #Selecciono mas de un proyecto
        error = "Debe seleccionar un unico proyecto"
        return error

    return error

def listar_miembros_comite(nombre_proyecto):
    lista = []

    proyecto = db_session.query(Proyecto).filter_by(\
    nombre=nombre_proyecto).one()

    lista_miembros = proyecto.miembros_comite

    for miembro in lista_miembros:
        elem = dict(id_usuario=miembro.id_usuario, \
        nombre=miembro.nombre, apellido=miembro.apellido)
        lista.append(elem)

    lista = sorted(lista, key=lambda elemento: elemento['id_usuario'])
    return lista

def listar_candidatos(nombre_proyecto):
    lista = []

    proyecto = db_session.query(Proyecto).filter_by(\
    nombre=nombre_proyecto).one()

    lista_miembros_comite = proyecto.miembros_comite
    lista_miembros = proyecto.miembros

    for miembro in lista_miembros:
        existe = False
        for miembro_comite in lista_miembros_comite:
            if miembro_comite.id_usuario == miembro.id_usuario:
                existe = True

        if not existe:
            elem = dict(id_usuario=miembro.id_usuario, \
            nombre=miembro.nombre, apellido=miembro.apellido)
            lista.append(elem)

    lista = sorted(lista, key=lambda elemento: elemento['id_usuario'])
    return lista

def validar_seleccion_candidatos_comite(seleccionados):
    error = None
    if len(seleccionados) == 0: #No selecciono ningun usuario
        error = "Debe seleccionar al menos un usuario"
        return error

    return error

def asignar_miembros_comite(nombre_proyecto, seleccionados):
    try:
        proyecto = db_session.query(Proyecto).filter_by\
        (nombre=nombre_proyecto).one()

        for elemento in seleccionados:
            usuario = db_session.query(Usuario).filter_by\
            (id_usuario=elemento).one()

            proyecto.miembros_comite.append(usuario)
            db_session.flush()

        #Se cancelan las solicitudes de cambio pendientes
        lista_solicitudes = db_session.query(Solicitud).filter_by\
        (fk_proyecto=proyecto.id).all()

        mensaje = "[Solicitud cancelada por cambios en el comite] "
        for solicitud in lista_solicitudes:
            if solicitud.estado == "PEN":
                solicitud.descripcion = mensaje + solicitud.descripcion
                solicitud.estado = "CAN"
                db_session.add(solicitud)
                db_session.flush()

        db_session.commit()
    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return 'OK'

def validar_seleccion_desasignar_comite(nombre_proyecto, seleccionados):
    """Verifica que se haya seleccionado al menos un usuario. Ademas, 
    verifica que el usuario a desasignar no sea el lider del proyecto
    @param nombre_proyecto: Nombre del proyecto actual
    @param seleccionados: Lista de usuarios seleccionados"""
    error = None
    if len(seleccionados) == 0: #No selecciono ningun usuario
        error = "Debe seleccionar al menos un usuario"
        return error

    proyecto = db_session.query(Proyecto).filter_by\
    (nombre=nombre_proyecto).one()

    lider = db_session.query(Usuario).filter_by\
    (id=proyecto.lider).one()

    for elemento in seleccionados:
        if elemento == lider.id_usuario:
            error = "El lider del proyecto no puede desasignarse"
            return error

    return error

def desasignar_miembros_comite(nombre_proyecto, seleccionados):
    """Elimina a los miembros seleccionados del comite
    @param nombre_proyecto: Nombre del proyecto actual
    @param seleccionados: Lista de usuarios seleccionados"""
    try:
        proyecto = db_session.query(Proyecto).filter_by\
        (nombre=nombre_proyecto).one()

        #Se excluye del comite a los usuarios seleccionados
        for elemento in seleccionados:
            usuario = db_session.query(Usuario).filter_by\
            (id_usuario=elemento).one()

            proyecto.miembros_comite.remove(usuario)
            db_session.flush()

        #Se cancelan las solicitudes de cambio pendientes
        lista_solicitudes = db_session.query(Solicitud).filter_by\
        (fk_proyecto=proyecto.id).all()

        mensaje = "[Solicitud cancelada por cambios en el comite] "
        for solicitud in lista_solicitudes:
            if solicitud.estado == "PEN":
                solicitud.descripcion = mensaje + solicitud.descripcion
                solicitud.estado = "CAN"
                db_session.add(solicitud)
                db_session.flush()

        db_session.commit()
    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return 'OK'

def validaciones_boton_votar_solicitud(seleccionados, id_usuario, \
    nombre_proyecto):
    """Verifica que se haya seleccionado una solicitud y solo una. 
    Ademas de que la solicitud se encuentre Pendiente, y que el 
    votante sea un miembro del comite que aun no haya votado
    @param seleccionados: Lista de solicitudes seleccionadas
    @param id_usuario: Usuario anctual
    @param nombre_proyecto: Nombre del proyecto actual"""
    error = None
    if len(seleccionados) == 0: #No selecciono ninguna solicitud
        error = "Debe seleccionar una solicitud"
        return error
    if len(seleccionados) > 1: #Selecciono mas de una solicitud
        error = "Debe seleccionar una unica solicitud"
        return error

    proyecto = db_session.query(Proyecto).filter_by\
    (nombre=nombre_proyecto).one()

    if proyecto.estado == 'FIN':
        error = "El proyecto se encuentra Finalizado"
        return error

    id_solicitud = int(seleccionados[0])

    solicitud = db_session.query(Solicitud).filter_by\
    (id=id_solicitud).one()
    if solicitud.estado != 'PEN':
        error = "Solo pueden votarse solicitudes pendientes"
        return error

    usuario = db_session.query(Usuario).filter_by\
    (id_usuario=id_usuario).one()

    lista_votantes = proyecto.miembros_comite
    continuar = False
    for votante in lista_votantes:
        if usuario.id == votante.id:
            continuar = True
            break

    if not continuar:
        error = "El usuario actual no es miembro del comite de cambio \
        del proyecto"
        return error

    voto = db_session.query(Voto).filter_by\
    (fk_solicitud=id_solicitud, fk_usuario=usuario.id).first()

    if voto != None:
        error = "El usuario actual ya voto la solicitud seleccionada"
        return error

    return error

def insertar_voto(id_solicitud, id_usuario, voto, observacion):
    """Registra el voto en la base de datos
    @param id_solicitud: Identificador de la solicitud que se vota
    @param id_usuario: Usuario que emite el voto
    @param observacion: Observacion sobre el voto emitido"""
    try:
        fecha_actual = datetime.now()

        eleccion = None
        if voto == "A favor":
            eleccion = "F"
            solicitud = db_session.query(Solicitud).filter_by\
            (id=id_solicitud).one()
            solicitud.votos_a_favor = solicitud.votos_a_favor + 1
            db_session.add(solicitud)
            db_session.flush()

        if voto == "En contra":
            eleccion = "C"

        usuario = db_session.query(Usuario).filter_by\
        (id_usuario=id_usuario).one()

        voto = Voto()
        voto.fk_solicitud = id_solicitud
        voto.fk_usuario = usuario.id
        voto.observacion = observacion
        voto.opcion = eleccion
        voto.fecha = fecha_actual

        db_session.add(voto)
        db_session.flush()

        db_session.commit()
    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return 'OK'

def conteo_votos(id_solicitud, nombre_proyecto):
    """Realiza el conteo total de votos, para luego proceder a la 
    aprobacion o rechazo de la solicitud
    @param id_solicitud: Identificador de la solicitud
    @param nombre_proyecto: Nombre del proyecto actual"""
    try:
        error = None
        lista_votos = db_session.query(Voto).filter_by\
        (fk_solicitud=id_solicitud).all()

        proyecto = db_session.query(Proyecto).filter_by\
        (nombre=nombre_proyecto).one()

        total_comite = len(proyecto.miembros_comite)
        numero_necesario = (total_comite / 2) + 1

        a_favor = 0
        en_contra = 0
        for voto in lista_votos:
            if voto.opcion == 'F':
                a_favor = a_favor + 1
            if voto.opcion == 'C':
                en_contra = en_contra + 1

        solicitud = db_session.query(Solicitud).filter_by\
        (id=id_solicitud).one()

        if a_favor == numero_necesario:
            error = ejecutar_cambio(id_solicitud)

            if error != "OK":
                return error

            solicitud.estado = "APR"

        if en_contra == numero_necesario:
            solicitud.estado = "NEG"

        db_session.add(solicitud)
        db_session.flush()

        db_session.commit()
    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return "OK"

def ejecutar_cambio(id_solicitud):
    """Realiza las tareas posteriores a la Aprobacion de la solicitud, 
    por cada item de la misma. 
    1. Se activa la fase en la que se encuentra el item, y las
    que vienen a continuacion
    2. Se establece en estado de revision a los items hijos y
    sucesores
    3. Se establece en estado Aprobado los items miembros de las lineas 
    base que contiene uno o mas items en Revision
    4. Se elimina la linea base que contiene al item
    5. Se establece en estado Activo el item actual
    @param id_solicitud: Identificador de la solicitud actual"""
    try:
        error = None
        solicitud = db_session.query(Solicitud).filter_by\
        (id=id_solicitud).one()

        for item in solicitud.items:
            #Se activa la fase en la que se encuentra el item, y las
            #que vienen a continuacion
            error = ec_activar_fase(item)
            if error != "OK":
                db_session.rollback()
                return error

            #Se establece en estado de revision a los items hijos y
            #sucesores
            error = items_mgr.establecer_estado_revision(item.id)
            if error != "OK":
                db_session.rollback()
                return error

            #Se establece en estado Aprobado los items miembros de las lineas 
            #base que contiene uno o mas items en Revision
            error = items_mgr.establecer_estado_aprobado()
            if error != "OK":
                db_session.rollback()
                return error

            #Se establece en estado ELI la linea base que contiene al item
            for lb in item.lineabase:
                error = ec_eliminar_lineabase(lb)
                if error != "OK":
                    db_session.rollback()
                    return error

            #Se cancelan las demas solicitudes sobre el item actual
            error = cancelar_solicitudes_nue_pen(item.id, id_solicitud)
            if error != "OK":
                db_session.rollback()
                return error

            #Se establece en estado Activo el item actual
            item.estado = "ACT"
            db_session.add(item)
            db_session.flush()

        db_session.commit()
    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return "OK"

def ec_activar_fase(item):
    """Activa la Fase en la que se encuentra el item seleccionado
    @param item: Item seleccionado"""
    try:
        tipo_item = db_session.query(TipoItem).filter_by\
        (id=item.fk_tipo_item).one()

        fase = db_session.query(Fase).filter_by\
        (id=tipo_item.fk_fase).one()

        proyecto = db_session.query(Proyecto).filter_by\
        (id=fase.fk_proyecto).one()

        for elem_fase in proyecto.fases:
            if elem_fase.orden >= fase.orden and \
            elem_fase.estado == "FIN":
                elem_fase.estado == "ACT"
                db_session.add(elem_fase)
                db_session.flush()

    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return 'OK'

def ec_eliminar_lineabase(linea_base):
    """Establece en estado ELI la lineabase seleccionada
    @param linea_base: Linea base seleccionada"""
    try:
        for item in linea_base.items:
            if item.estado == 'BLO':
                item.estado = 'APR'

                db_session.add(item)
                db_session.flush()

        linea_base.estado = "ELI"
        db_session.add(linea_base)
        db_session.flush()

    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return 'OK'

def conteo_eliminar_lineasbase():
    """Elimina fisicamente las lineas base con estado ELI"""
    try:
        lineas_base = db_session.query(LineaBase).filter_by\
        (estado="ELI").all()

        for lb in lineas_base:
            db_session.delete(lb)
            db_session.flush()

    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return 'OK'

def validaciones_boton_crear_solicitud(nombre_proyecto):
    """Valida que el proyecto en el cual se quiere crear la solicitud, 
    no se encuentre Finalizado
    @param nombre_proyecto: Nombre del proyecto actual"""
    error = None

    proyecto = db_session.query(Proyecto).filter_by\
    (nombre=nombre_proyecto).one()

    if proyecto.estado == 'FIN':
        error = "El proyecto se encuentra Finalizado"
        return error

    return error

def obtener_items_afectados(id_solicitud, nombre_proyecto):
    """Obtiene la lista de los items afectados por la solicitud 
    seleccionada
    @param id_solicitud: Identificador de la solicitud de cambio
    @param nombre_proyecto: Nombre del proyecto actual
    @return lista: Lista de items afectados por la solicitud"""
    solicitud = db_session.query(Solicitud).filter_by(\
    id=id_solicitud).one()

    usuario_creador = db_session.query(Usuario).filter_by(\
    id=solicitud.usuario_creacion).one()

    lista_items = solicitud.items

    lista_aux = []
    for item_proyecto in lista_items:
        sw = False
        identificador = item_proyecto.id
        for item_solicitud in solicitud.items:
            if identificador == item_solicitud.id:
                recorrer_items(identificador, lista_aux, identificador)

    lista = []
    for elemento in lista_aux:
        id_item = elemento['id']
        item_base = elemento['item_base']

        item = db_session.query(Item).filter_by(\
        id=id_item).one()

        tipo = db_session.query(TipoItem).filter_by(\
        id=item.fk_tipo_item).one()

        fase = db_session.query(Fase).filter_by(\
        id=tipo.fk_fase).one()

        elem = dict(id=item.id, item_base=item_base, nombre=\
        item.nombre, id_fase=fase.id, nombre_fase=fase.nombre, \
        orden_fase=fase.orden)
        lista.append(elem)

    lista = sorted(lista, key=lambda elemento: elemento['orden_fase'])
    return lista

def recorrer_items(id_item, lista_items, item_base):
    relaciones = db_session.query(Relacion).filter_by(\
    fk_item_origen=id_item).all()

    for relacion in relaciones:
        resultado = recorrer_items(relacion.fk_item_destino, \
        lista_items, item_base)
        if resultado == 'OK':
            item = db_session.query(Item).filter_by(\
            id=relacion.fk_item_destino).one()

            if item.estado != 'ELI':
                elemento = dict(id=item.id, item_base=item_base)
                lista_items.append(elemento)

        else:
            return resultado

    return 'OK'

def cancelar_solicitudes_nue_pen(id_item, id_solicitud):
    """Cancela las demas solicitudes sobre el item actual
    @param id_item: Identificador del item actual
    @param id_solicitud: Identificador de la solicitud actual"""
    try:
        item = db_session.query(Item).filter_by(id=id_item).one()

        for solicitud in item.solicitud:
            if solicitud != 'APR' and solicitud.id != id_solicitud:
                mensaje = "[Solicitud cancelada por aprobacion previa \
                de otra solicitud sobre el item '" + item.nombre + "'] "
                solicitud.descripcion = mensaje + solicitud.descripcion
                solicitud.estado = "CAN"

                db_session.add(solicitud)
                db_session.flush()

    except exc.SQLAlchemyError, e:
        db_session.rollback()
        return e.message

    return 'OK'
